﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Globalization;
using ScrumTable.Common.Collections;
using ScrumTable.Common.Image;

#endregion

namespace ScrumTable.Common
{
    /// <summary>
    /// Contains the global utilities for the object class.
    /// </summary>
    public static class ObjectUtil
    {
        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Releases an object reference.
        /// </summary>
        /// <param name="toRelease">Specifies the reference to release.</param>
        public static void ReleaseReference(ref object toRelease)
        {
            if (toRelease != null && toRelease is IDisposable)
            {
                ((IDisposable) toRelease).Dispose();
            }
            toRelease = null;
        }

        /// <summary>
        /// Tries to change the type from an object.
        /// </summary>
        /// <param name="toChangeType">Specifies the object which type should be changed.</param>
        /// <param name="resultValue">Specifies a reference to the result value variable.</param>
        /// <typeparam name="T">Specifies the resulting type.</typeparam>
        /// <returns>Returns true if the change process completed successfully.</returns>
        public static bool TryChangeType<T>(this object toChangeType, out T resultValue)
        {
            resultValue = default(T);
            object convertedType;

            if (TryChangeType(toChangeType, typeof(T), out convertedType))
            {
                resultValue = (T)convertedType;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Tries to change the type from an object.
        /// </summary>
        /// <param name="toChangeType">Specifies the object which type should be changed.</param>
        /// <param name="resultType">Specifies the resulting type.</param>
        /// <param name="resultValue">Specifies a reference to the result value variable.</param>
        /// <returns>Returns true if the change process completed successfully.</returns>
        public static bool TryChangeType(this object toChangeType, Type resultType, out object resultValue)
        {
            resultValue = resultType.GetDefault();

            if (toChangeType != null)
            {
                if (resultType.IsInstanceOfType(toChangeType))
                {
                    resultValue = toChangeType;
                    return true;
                }
                else
                {
                    try
                    {
                        resultValue = toChangeType.ChangeType(resultType);
                        return true;
                    }
                    catch (InvalidCastException)
                    {
                        return false; // expected error if conversion fails
                    }
                    catch (FormatException)
                    {
                        return false; // expected error if conversion fails
                    }
                    catch (OverflowException)
                    {
                        return false; // expected error if conversion fails
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Changes the type from a object
        /// </summary>
        /// <typeparam name="T">Specifies the resulting type.</typeparam>
        /// <param name="toChangeType">Specifies the object which type should be changed.</param>
        public static T ChangeType<T>(this object toChangeType)
        {
            if (toChangeType == null)
            {
                return default(T);
            }
            if (toChangeType is T)
            {
                return (T)toChangeType;
            }
            return (T)Convert.ChangeType(toChangeType, typeof(T), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Changes the type from a object
        /// </summary>
        /// <param name="toChangeType">Specifies the object which type should be changed.</param>
        /// <param name="type">Specifies the resulting type.</param>
        public static object ChangeType(this object toChangeType, Type type)
        {
            PreCondition.AssertNotNull(type, "type");
            type = Nullable.GetUnderlyingType(type) ?? type;

            if (toChangeType == null)
            {
                return type.GetDefault();
            }
            if (toChangeType.GetType().IsAssignableFrom(type))
            {
                return toChangeType;
            }
            return Convert.ChangeType(toChangeType, type, CultureInfo.InvariantCulture);
        }


        /// <summary>
        /// Changes the type from a object. It checks if the value is a empty string and convert it into a right nullable object
        /// </summary>
        /// <param name="toChangeType">Specifies the object which type should be changed.</param>
        /// <param name="type">Specifies the resulting type.</param>
        public static object ChangeTypeWithNullableString(this object toChangeType, Type type)
        {
            PreCondition.AssertNotNull(type, "type");
            var nullableUnderlyingType = Nullable.GetUnderlyingType(type);
            if (nullableUnderlyingType == null || !(toChangeType is string))
            {
                return ChangeType(toChangeType, type);
            }
            else
            {
                if (string.IsNullOrEmpty(toChangeType.ToString()))
                {
                    return type.GetDefault();
                }
            }

            return ChangeType(toChangeType, type);
        }

        /// <summary>
        /// Changes the type from object to string.
        /// </summary>
        /// <param name="data">Specifies the object which should be converted into a string.</param>
        /// <param name="mustResolve">Specifies that the given object must be resolved with the additional resolve method.</param>
        /// <param name="resolve">Specifies the method to resolve the underlying data and return the string from there.</param>
        public static string ConvertToString(object data, Predicate<object> mustResolve = null, Func<object, string> resolve = null)
        {
            if (data == null)
            {
                return null;
            }
            if (mustResolve != null && resolve != null && mustResolve(data))
            {
                return resolve(data);
            }
            if (data is string)
            {
                return (string)data;
            }
            if (data is System.Drawing.Image)
            {
                return ((System.Drawing.Image)data).ToLineBreakString();
            }
            if (data is ValueChain<string>)
            {
                return ValueChainSerializer.Serialize((ValueChain<string>)data);
            }
            if (data is Uri)
            {
                return data.ToString();
            }
            if (data is ValueRange<string>)
            {
                return ((ValueRange<string>)data).SingleValue;
            }
            if (data is Type)
            {
                return ((Type)data).AssemblyQualifiedName;
            }
            if (data is Enum)
            {
                return Enum.GetName(data.GetType(), data);
            }
            return ChangeType<string>(data);
        }

        /// <summary>
        /// Changes the type from object to string.
        /// </summary>
        /// <param name="input">Specifies the string which should be converted into an object.</param>
        /// <typeparam name="T">Specifies the resulting type.</typeparam>
        public static T ConvertFromString<T>(string input)
        {
            object result = ConvertFromString(input, typeof (T));
            return (result != null) ? (T)result : default(T);
        }

        /// <summary>
        /// Changes the type from object to string.
        /// </summary>
        /// <param name="input">Specifies the string which should be converted into an object.</param>
        /// <param name="targetType">Specifies the resulting type.</param>
        public static object ConvertFromString(string input, Type targetType)
        {
            PreCondition.AssertNotNull(targetType, "targetType");

            if (string.IsNullOrEmpty(input))
            {
                return null;
            }
            if (typeof(string).IsAssignableFrom(targetType))
            {
                return input;
            }
            if (typeof(System.Drawing.Image).IsAssignableFrom(targetType))
            {
                return ImageUtil.FromLineBreakString(input);
            }
            if (typeof(ValueChain<string>).IsAssignableFrom(targetType))
            {
                return ValueChainSerializer.Parse(input);
            }
            if (typeof(Uri).IsAssignableFrom(targetType))
            {
                return new Uri(input, UriKind.RelativeOrAbsolute);
            }
            if (typeof(ValueRange<string>).IsAssignableFrom(targetType))
            {
                return ValueRange<string>.Parse(input);
            }
            if (typeof(Type).IsAssignableFrom(targetType))
            {
                return TypeUtil.GetType(input);
            }
            if (typeof(Enum).IsAssignableFrom(targetType))
            {
                return Enum.Parse(targetType, input);
            }
            return ChangeType(input, targetType);
        }

        /// <summary>
        /// Changes the type from object to string.
        /// </summary>
        /// <param name="input">Specifies the string which should be converted into an object.</param>
        /// <param name="value">Specifies the output value to retrieve.</param>
        /// <typeparam name="T">Specifies the resulting type.</typeparam>
        public static bool TryConvertFromString<T>(string input, out T value)
        {
            try
            {
                value = ConvertFromString<T>(input);
                return true;
            }
            catch (InvalidCastException)
            {
                value = default(T);
                return false;
            }
            catch (FormatException)
            {
                value = default(T);
                return false;
            }
            catch (OverflowException)
            {
                value = default(T);
                return false;
            }
        }

        /// <summary>
        /// Changes the type from object to string.
        /// </summary>
        /// <param name="outputType">Specifies the resulting type.</param>
        /// <param name="input">Specifies the string which should be converted into an object.</param>
        /// <param name="value">Specifies the output value to retrieve.</param>
        public static bool TryConvertFromString(Type outputType, string input, out object value)
        {
            try
            {
                value = ConvertFromString(input, outputType);
                return true;
            }
            catch (InvalidCastException)
            {
                value = null;
                return false;
            }
            catch (FormatException)
            {
                value = null;
                return false;
            }
            catch (OverflowException)
            {
                value = null;
                return false;
            }
        }

        #endregion
    }
}
